﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.Xna.Framework;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Diagnostics;
using System.Xml.Linq;

namespace Pavilion
{
    static public class GameEngine
    {
        static private Game Game { get; set; }
        static public GameWindow Window { get { return Game.Window; } }
        static public IServiceProvider Services { get; private set; }
        static public string MainScript { get; set; }
        static public int WindowWidth { get { return Window.ClientBounds.Width; } }
        static public int WindowHeight { get { return Window.ClientBounds.Height; } }
        static public bool IsMouseVisible { get { return Game.IsMouseVisible; } set { Game.IsMouseVisible = value; } }

        static public void Initialize(Game game, IServiceProvider services, GraphicsDevice graphics, string contentDirectory)
        {
            GameEngine.Game = game;
            GameEngine.ContentDirectory = contentDirectory;
            GameEngine.Services = services;
            GameEngine.MainScript = XDocument.Load(GameEngine.ContentDirectory + "GameSettings.xml").Element("Settings").Element("MainScript").Value;

            GameServiceContainer serviceContainer = new GameServiceContainer();
            DrawManager.Instance.Initialize(graphics);
        }

        static public Type GetType(string assemblyName, string typeName)
        {
            string assemblyNameWithoutExt = assemblyName.Substring(0, assemblyName.LastIndexOf(".") + 1);

            Assembly assembly = Assembly.LoadFile(Environment.CurrentDirectory + @"\" + assemblyName);

            return assembly.GetType(assemblyNameWithoutExt + typeName);
        }

        /// <summary>
        /// Creates an instance from the local directory.
        /// </summary>
        /// <param name="assemblyName">The name of the assembly. Ex: Pavilion.Locks.dll</param>
        /// <param name="typeName">The name of the type. Ex: bool</param>
        static public T CreateLocalInstance<T>(string assemblyName, string typeName)
        {
            string assemblyNameWithoutExt = assemblyName.Substring(0, assemblyName.LastIndexOf(".") + 1);

            Assembly assembly = Assembly.LoadFile(Environment.CurrentDirectory + @"\" + assemblyName);
            T instance = (T)Activator.CreateInstance(assembly.GetType(assemblyNameWithoutExt + typeName));
            
            return instance;
        }

        /// <summary>
        /// Creates an instance from the local directory.
        /// </summary>
        /// <param name="assemblyName">The name of the assembly. Ex: Pavilion.Locks.dll</param>
        /// <param name="typeName">The name of the type. Ex: bool</param>
        /// /// <param name="parameters">Paramters for the constructor of the type that is to be created.</param>
        static public T CreateLocalInstance<T>(string assemblyName, string typeName, params object[] parameters)
        {
            string assemblyNameWithoutExt = assemblyName.Substring(0, assemblyName.LastIndexOf(".") + 1);

            Assembly assembly = Assembly.LoadFile(Environment.CurrentDirectory + @"\" + assemblyName); ;
            T instance = (T)Activator.CreateInstance(assembly.GetType(assemblyNameWithoutExt + typeName), parameters);

            return instance;
        }

        static public object CreateInstance(Type type)
        {
            return CreateInstance<object>(type.Assembly.ManifestModule.FullyQualifiedName, type.FullName);
        }

        static public T CreateInstance<T>(string fullAssemblyName, string fullTypeName)
        {
            if (fullTypeName == "System.String")
            {
                object objectValue = "";
                return (T)objectValue;
            }

            Assembly assembly = Assembly.LoadFile(fullAssemblyName);
            Type assemblyType = assembly.GetType(fullTypeName);

            if (assemblyType == typeof(float[]))
            {
                object returnValue = new float[] { };
                return (T)returnValue;
            }

            T instance = (T)Activator.CreateInstance(assemblyType);

            return instance;
        }

        static public IEnumerable<Type> ObtainAllTypes<T>(string assemblyName)
        {
            Assembly assembly = Assembly.LoadFile(Environment.CurrentDirectory + @"\" + assemblyName);

            return assembly.GetTypes().Where(type => typeof(T).IsAssignableFrom(type)).ToArray();
        }

        static public object ParseObject(string typeName, string propertyValue)
        {
            Type type = Type.GetType(typeName);

            if (type == null)
            {
                if (typeName == "Microsoft.Xna.Framework.Input.Keys")
                    type = Microsoft.Xna.Framework.Input.Keys.A.GetType();
                else
                {
                    int index = typeName.LastIndexOf(".");
                    string assemblyName = typeName.Substring(0, index);

                    type = Assembly.Load(assemblyName).GetType(typeName);
                }
            }

            return ParseObject(type, propertyValue);
        }

        static public object ParseObject(Type type, string propertyValue)
        {
            if (type == typeof(string))
                return propertyValue;
            else if (type.BaseType == typeof(Enum) || type == typeof(Enum))
                return Enum.Parse(type, propertyValue);
            else if (type == typeof(bool))
                return bool.Parse(propertyValue);
            else if (type == typeof(int))
                return int.Parse(propertyValue);
            else if (type == typeof(float))
                return float.Parse(propertyValue);
            else if (type == typeof(float[]))
                return ParseFloatCollection(propertyValue);
            else if (type == typeof(Vector2))
                return ParseVectorValue(propertyValue);
            else if (type == typeof(double))
                return double.Parse(propertyValue);
            else if (type == typeof(char))
                return char.Parse(propertyValue);
            else if (type == typeof(long))
                return long.Parse(propertyValue);
            else if (type == typeof(short))
                return short.Parse(propertyValue);

            throw new Exception("Could not parse value. The typeName '" + type +
                "' is not supported. Go to the 'ParseObject' method within the 'GameEngine' class under the 'Pavilion' project and add the following typeName so it is supported! :)");
        }

        static public T ParseObject<T>(string propertyValue)
        {
            return (T)ParseObject(typeof(T), propertyValue);
        }

        static public string GetStringRepresention(object value)
        {
            if (value is float[])
            {
                string returnValue = "";

                foreach (float item in (float[])value)
                    returnValue += item + ", ";

                if (returnValue.Length == 0)
                    return returnValue;
                else
                    return returnValue.Remove(returnValue.Length - 2);
            }
            else if (value is Vector2)
            {
                Vector2 vector = (Vector2)value;

                return vector.X + "," + vector.Y;
            }
            else if (value == null)
                return null;
            else
                return value.ToString();
        }

        static public float[] ParseFloatCollection(string floatCollection)
        {
            if (string.IsNullOrEmpty(floatCollection))
                return new float[] { };

            string[] values = floatCollection.Split(',');
            float[] floatValues = new float[values.Length];

            for (int i = 0; i < values.Length; i++)
                floatValues[i] = float.Parse(values[i]);

            return floatValues;
        }

        private static Vector2 ParseVectorValue(string vectorValues)
        {
            float[] positionValues = ParseFloatCollection(vectorValues);

            return new Vector2(positionValues[0], positionValues[1]);
        }

        static public IEnumerable<string> GetResourceItemList()
        {
            return GetDirectoryNames(GameEngine.AnimationsDirectory);
        }

        static public IEnumerable<string> GetAnimations(string characterName)
        {
            return GetDirectoryNames(GameEngine.AnimationsDirectory + characterName + @"\Built Data");
        }

        static public IEnumerable<string> GetFileNames(string path, string extension)
        {
            string[] filePathList = Directory.GetFiles(path);

            foreach (string filePath in filePathList)
            {
                int firstIndex = filePath.LastIndexOf(@"\") + 1;
                int nameLenght = filePath.LastIndexOf(extension) - firstIndex;
                yield return filePath.Substring(firstIndex, nameLenght);
            }
        }

        static public IEnumerable<string> GetDirectoryNames(string path)
        {
            string[] directoryPathList = Directory.GetDirectories(path);

            foreach (string directoryPath in directoryPathList)
                yield return directoryPath.Substring(directoryPath.LastIndexOf(@"\") + 1);
        }

        static public string GetUniqueName(IEnumerable<string> collection, string prefix)
        {
            int count = 0;
            string name = prefix;

            while (collection.Contains(name))
                name = prefix + count++;

            return name;
        }

        static public T EnumParse<T>(string value)
        {
            return (T)Enum.Parse(typeof(T), value);
        }

        /// <summary>
        /// Gets the Content Directory of this Game.
        /// </summary>
        static public string ContentDirectory { get; private set; }

        /// <summary>
        /// Gets the resource path of where all the content resides.
        /// ContentDirectory + @"GameResources\";
        /// </summary>
        static public string GameResourcesDirectory { get { return ContentDirectory + @"GameResources\"; } }

        /// <summary>
        /// Gets the resource path of where all the content resides.
        /// ContentDirectory + @"GameResources\Animations\";
        /// </summary>
        static public string AnimationsDirectory {  get { return ContentDirectory + @"GameResources\Animations\"; } }

        /// <summary>
        /// Gets the Node Layout Directory path where all the scripts for node layouts reside.
        /// ContentDirectory + @"LayoutResources\Animations\";
        /// </summary>
        static public string NodeLayoutDirectory { get { return ContentDirectory + @"LayoutResources\Animations\"; } }
    }
}
